# -*- coding: utf-8 -*-

import tushare as ts
import datetime
import pandas as pd
from generate_signal import stockPurchasingDF
from ceshi_shuju import Shuju
import copy
"""
————————下单类————————
Orders类，包含一系列交易的函数和参数，用于按照指定要求买入和卖出，
对每一笔指定交易，对现有持仓current_position中的「现金数额和股票价值」进行调整。
具体的交易函数分别有以下两类。
第一类：交易费用---buy_commission,sell_commission
第二类：下单---
        买/卖指定金额：buy_value, sell_value
        买/卖指定股数: buy_volume, sell_volume
        买/卖至相应市值：buy_target_value, sell_target_value
相关参数：印花税率0.001（仅卖出时）
        佣金率0.0003
        过户费率0.0002
"""


class Orders:
    def __init__(self):
        self.current_position = current_position
        self.pd_log = pd_log
    # 根据买入价值，缴纳佣金和过户费

    def buy_commission(mairujiazhi):
        if mairujiazhi > 0:
            commission = mairujiazhi * (yongjinlv + guohufeilv)
            return commission

    # 根据卖出价值，缴纳佣金，过户费和印花税
    def sell_commission(mairujiazhi):
        if mairujiazhi > 0:
            commission = mairujiazhi * (yongjinlv + guohufeilv + yinhuashuilv)
            return commission

    # 根据买入股数建立函数，调整现有持仓
    def buy_volume(day, code, buy_volume):
        global current_position,pd_log
        price = Shuju.get_close_price(code, day)
        volume = round((buy_volume/100))*100
        spending = price*volume
        pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "买入", "成交数量": volume, "成交价格": price, "成交额": spending}, ignore_index=True)
        today_hold = []
        for i in current_position:
            today_hold.append(i["股票代码"])
        # 1 判断是否足够购买，先提取账面现金数值
        for i in current_position:
            if i["股票代码"] == "cash":
                temp_cash = i["持仓价值"]
        # 2 判断是否足够购买，如果足够则继续。
        if spending <= temp_cash:
            # 2.1 扣钱
            for i in current_position:
                if i["股票代码"] == "cash":
                    commission = Orders.buy_commission(spending)
                    i["持仓价值"] = i["持仓价值"] - spending - commission
            # 2.2 对于新购买的情形
            if code not in today_hold:
                current_position = current_position.append({"股票代码": code, "持仓数量": volume, "持仓价值": spending, "持仓天数": 0})
            # 2.3 对于增持的情形
            else:
                for i in current_position:
                    if i["股票代码"] == code:
                        i["持仓数量"] += volume
                        i["持仓价值"] += price * i["持仓数量"]
            # 2.4 记录日志
            pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "买入", "成交数量": volume, "成交价格": price, "成交额": spending}, ignore_index=True)
        else:
            print("购买资金不足")

    # 根据卖出股数建立函数，调整现有持仓
    def sell_volume(day,code,sell_volume):
        global current_position,pd_log
        price = Shuju.get_close_price(code, day)
        sell_volume = round(sell_volume / 100) * 100  # 向下取整百
        temp_volume = False
        # 1 判断是否足够卖出（不考虑卖空），先提取该股票的持仓数量
        for i in current_position:
            if i["股票代码"] == code:
                temp_volume = i["持仓数量"]
        # 如果temp_volume被创建出来，应该不是False, 才继续执行以下语句，否则结束该函数。（也就是只有具有仓位，才进行卖出操作。）
        if temp_volume != False:
        # 2 判断是否足够购买，如果持仓量大于零，且sell小于持仓量，则卖出sell_volume。
            if temp_volume > 0 and sell_volume < temp_volume:
        # 2.1 现金增加
                earnings = sell_volume * price
                for i in current_position:
                    if i["股票代码"] == "cash":
                        commission = Orders.sell_commission(earnings)
                        i["持仓价值"] = i["持仓价值"] + earnings - commission
        # 2.2 仓位减持
                for i in current_position:
                    if i["股票代码"] == code:
                        i["持仓数量"] -= sell_volume
                        i["持仓价值"] -= earnings
        # 2.3 记录日志
                pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "卖出", "成交数量": sell_volume, "成交价格": price, "成交额": earnings},ignore_index=True)

        # 3 判断是否足够购买，如果持仓量大于零，且sell大于等于持仓量，则卖出temp_volume，在current_position中抹去该持仓。
            if temp_volume > 0 and sell_volume >= temp_volume:
            # 3.1 现金增加
                earnings = temp_volume*price
                for i in current_position:
                    if i["股票代码"] == "cash":
                        commission = Orders.sell_commission(earnings)
                        i["持仓价值"] = i["持仓价值"] + earnings - commission
            # 3.2 清空仓位
                for i in current_position:
                    if i["股票代码"] == code:
                        current_position.remove(i)
        # 3.3 记录日志
                pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "卖出", "成交数量": temp_volume, "成交价格": price, "成交额": earnings},ignore_index=True)

    #根据买入价值建立函数，调整现有持仓
    def buy_value(time, code, value):
        global current_position, pd_log
        price = Shuju.get_close_price(code, time,conStockData)
        volume = ((value / price) // 100) * 100  # 向下取整百
        spending = price * volume
        today_hold = []
        if len(current_position) > 0:
            for i in current_position:
                today_hold.append(i["股票代码"])

            # 1 判断是否足够购买，先提取账面现金数值
            for i in current_position:
                if i["股票代码"] == "cash":
                    temp_cash = i["持仓价值"]
            # 2 判断是否足够购买，如果足够则继续。
            if spending <= temp_cash:
                # 2.1 扣钱
                for i in current_position:
                    if i["股票代码"] == "cash":
                        commission = Orders.buy_commission(spending)
                        if commission is None:
                            commission = 0
                        i["持仓价值"] = i["持仓价值"] - spending - commission
                # 2.2 对于新购买的情形
                if code not in today_hold:
                    current_position.append({"股票代码": code, "持仓数量": volume, "持仓价值": spending, "持仓天数": 0})
                # 2.3 对于增持的情形
                else:
                    for i in current_position:
                        if i["股票代码"] == code:
                            i["持仓数量"] += volume
                            i["持仓价值"] += price * i["持仓数量"]
                # 2.4 记录日志
                pd_log = pd_log.append(
                    {"日期": time, "股票代码": code, "交易类型": "买入", "成交数量": volume, "成交价格": price, "成交额": spending},
                    ignore_index=True)
        return current_position, pd_log

    # 根据买入到目标价值建立函数，调整现有持仓
    def buy_target_value(day,code,target_value):
        global current_position,pd_log
        price = Shuju.get_close_price(code,day)
        target_volume = round((target_value/price)/100)*100  #四舍五入
        target_value = target_value*price
        today_hold = [i["股票代码"] for i in current_position]
        for i in current_position:
            if i["股票代码"] == "cash":
                temp_cash = i["持仓价值"]
        if code not in today_hold:
            if temp_cash > target_value:
                current_position = current_position.append({"股票代码": code, "持仓数量": target_volume, "持仓价值": target_volume*price, "持仓天数": 0})
                pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "买入", "成交数量": target_volume, "成交价格": price, "成交额": target_volume*price},ignore_index=True)
                for i in current_position:
                    if i["股票代码"] == "cash":
                        commission = Orders.buy_commission(target_value)
                        i["持仓价值"] = i["持仓价值"] - target_value - commission
            else:
                print("购买资金不足")
        else:
            for i in current_position:
                if i["股票代码"]==code:
                    if i["持仓价值"] < target_value:
                        buy_volume = target_volume - i["持仓数量"]
                        buy_value = buy_volume*price
                        if temp_cash > buy_value:
                            i["持仓数量"] = target_volume
                            i["持仓价值"] = target_value
                            pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "买入", "成交数量": buy_volume, "成交价格": price,"成交额": buy_value}, ignore_index=True)
                            for i in current_position:
                                if i["股票代码"] == "cash":
                                    commission = Orders.buy_commission(buy_value)
                                    i["持仓价值"] = i["持仓价值"] - buy_value - commission
                        else:
                            print("购买资金不足")
                    print("持有价值大于目标价值")

    #根据卖出价值建立函数，调整现有持仓

    def sell_value(time, code, value):
        global current_position, pd_log
        price = Shuju.get_close_price(code, time,conStockData)
        sell_volume = round((value / price) / 100) * 100  # 向下取整百
        temp_volume = False
        # 1 判断是否足够卖出（不考虑卖空），先提取该股票的持仓数量
        for i in current_position:
            if i["股票代码"] == code:
                temp_volume = i["持仓数量"]
        # 如果temp_volume被创建出来，应该不是False, 才继续执行以下语句，否则结束该函数。（也就是只有具有仓位，才进行卖出操作。）
        if temp_volume != False:
            # 2 判断是否足够购买，如果持仓量大于零，且sell小于持仓量，则卖出sell_volume。
            if temp_volume > 0 and sell_volume < temp_volume:
                # 2.1 现金增加
                for i in current_position:
                    if i["股票代码"] == "cash":
                        earnings = price * sell_volume
                        commission = Orders.sell_commission(earnings)
                        i["持仓价值"] = i["持仓价值"] + earnings - commission
                # 2.2 仓位减持
                for i in current_position:
                    if i["股票代码"] == code:
                        i["持仓数量"] -= sell_volume
                        i["持仓价值"] -= earnings
                # 2.3 记录日志
                pd_log = pd_log.append(
                    {"日期": time, "股票代码": code, "交易类型": "卖出", "成交数量": sell_volume, "成交价格": price, "成交额": earnings},
                    ignore_index=True)

            # 3 判断是否足够购买，如果持仓量大于零，且sell大于等于持仓量，则卖出temp_volume，在current_position中抹去该持仓。
            if (temp_volume > 0) and (sell_volume >= temp_volume) :
                # 3.1 现金增加
                for i in current_position:
                    if i["股票代码"] == "cash":
                        earnings = price * temp_volume
                        commission = Orders.sell_commission(earnings)
                        i["持仓价值"] = i["持仓价值"] + earnings - commission
                # 3.2 清空仓位
                for i in current_position:
                    if i["股票代码"] == code:
                        current_position.remove(i)
                # 3.3 记录日志
                pd_log = pd_log.append(
                    {"日期": time, "股票代码": code, "交易类型": "卖出", "成交数量": temp_volume, "成交价格": price, "成交额": earnings},
                    ignore_index=True)

    # 根据卖出到目标价值建立函数，调整现有持仓
    def sell_target_value(day, code, target_value):
        global current_position,pd_log
        price = Shuju.get_close_price(code, day)
        temp_value = False
        remain_volume = round(target_value / (price * 100)) * 100
        for i in current_position:
            if i["股票代码"] == code:
                temp_value = i["持仓价值"]
        if temp_value != False:
            if (temp_value > 0) and (target_value <= temp_value) and (target_value >= 0) :
                for i in current_position:
                    if i["股票代码"] == code:
                        sell_volume = i["持仓数量"] - remain_volume
                        earnings = sell_volume*price
                        i["持仓数量"] -= sell_volume
                        i["持仓价值"] -= earnings
                    if i["持仓数量"] <= 0 :
                        current_position.remove(i)
                for i in current_position:
                    if i["股票代码"] == "cash":
                        commission = Orders.sell_commission(earnings)
                        i["持仓价值"] = i["持仓价值"] + earnings - commission
                pd_log = pd_log.append({"日期": day, "股票代码": code, "交易类型": "卖出", "成交数量": sell_volume, "成交价格": price, "成交额": earnings },ignore_index=True)


"""
————————交易类————————
Trade类，包含一系列交易的函数和参数，用于解析策略，按照指定策略买入和卖出，
最后返回「随着时间的延长，持仓的改变情况」，并生成历史持仓数据（history_positions)用于回测部分分析。

初始化参数：
   1 当前持仓(current_position)，反映了当前的所有持仓，每天都会实时更新当前持仓。
            例如[{"股票代码":"cash", "持仓数量":None, "持仓价值":1000000, "持仓天数":None},
                    {"股票代码":"000001", "持仓数量":789, "持仓价值":54321, "持仓天数":1},...]
   2 历史持仓(history_positions)，用字典的形式，包含了过去每天的持仓情况，查找语法为：
            history_positions["20200831"]，返回在2020年八月三十一日的持仓情况。
   3 交易日志(pd_log), 详细记录了每一笔买卖的「日期、买/卖、股票代码、买入/卖出价值、成交数量、成交价格」

具体的交易函数分别有以下两类。 
第一类函数：解析策略---将dataframe格式的策略解析为每日的策略方案
        具体函数包括：get_new_code_list(),resolve_strategy(),
第二类函数：更新持仓函数——更新每天的市场价值，或者对单个股票的市值变动做出调整
        具体函数包括：market_value_of_position(),update_current_position(),update_history_position()
第三类函数：交易函数，解析指定的策略，调用orders进行下单，对回测期的股票进行买卖，得到history_positions用于回测分析。

"""


class Trade:
    def __init__(self):
        self.history_positions = history_positions
        self.pd_log = pd_log
        self.current_position = current_position

    def get_new_code_list(alist):
        blist=[]
        for i in alist:
            i.rstrip("\n")
            b = Shuju.update_stockcode(i)
            blist.append(b)
        return blist

    def resolve_strategy(strategy):
        strategy_dict = {}
        list_of_strategy_days = strategy["日期"].tolist()
        list_of_strategy_codes = strategy["买入的股票代码"].tolist()
        n = len(list_of_strategy_codes)
        print(n)
        for i in range(n):
            new_day = list_of_strategy_days[i]
            new_day = str(new_day)[:10]
            new_list = Trade.get_new_code_list(list_of_strategy_codes[i])
            formatted_new_day = new_day.split("-")[0] + new_day.split("-")[1] + new_day.split("-")[2]
            strategy_dict[formatted_new_day] = new_list
        return strategy_dict

    def market_value_of_position(position,time_datetime):
        market_value = 0
        for i in range(len(position)):
            if position[i]["股票代码"] == "cash":
                market_value += position[i]["持仓价值"]
                continue
            now_price = Shuju.get_close_price(position[i]["股票代码"],time_datetime,conStockData)
            market_value += position[i]["持仓数量"] * now_price
        return market_value

    def new_market_value_of_code(new,position,time_str):
        new_market_value = 0
        for i in position:
            if i["股票代码"] == new:
                new_price = Shuju.get_close_price(new,time_str,conStockData)
                new_market_value = new_price * i["持仓数量"]
                break
        return new_market_value

    def get_yyyymmdd_from_all_type(time):
        if len(list(str(time)))>8:
            time = str(time).split(" ")[0].split("-")[0] +str(time).split(" ")[0].split("-")[1]+str(time).split(" ")[0].split("-")[2]
            return time
        else:
            return time

    def update_current_position(current_position,tradeday):
        for i in current_position:
            if i["股票代码"] != "cash":
                price = Shuju.get_close_price(i["股票代码"], tradeday,conStockData)
                i["持仓天数"] = i["持仓天数"] + 1
                i["持仓价值"] = i["持仓数量"]*price
                if i["持仓数量"] == 0:
                    current_position.remove(i)
        return current_position

    def update_history_position(history_positions, current_position,tradeday):
        history_positions[tradeday] = current_position
        return history_positions


    def backtesting():
        global current_position, pd_log, history_positions,starting_cash
        strategy_dict = Trade.resolve_strategy(stockPurchasingDF)
        #开始日期
        #starting_day = kaishiriqi

    # 策略期时限
        len_of_days = len(strategy_dict)
    # 昨日策略列表(需要交叉比较今日策略列表和昨日列表，以实现不同处理）
        last_day_codes_list = []
    # 得到所有的交易日期（去除所有重复项）
        list_a = stockPurchasingDF["日期"].tolist()
        list_of_strategy_days = list(set(list_a))
        list_of_strategy_days.sort(key=list_a.index)
        startingday = starting_day
        endingday = ending_day
        # 如果设定开始时间不在交易日内，则获取里这时下一个交易日
        while startingday not in list_of_strategy_days:
            startingday = Shuju.get_next_trading(startingday)

        while endingday not in list_of_strategy_days:
            endingday = Shuju.get_next_trading(endingday)

        a = list_of_strategy_days.index(startingday)
        b= list_of_strategy_days.index(endingday)
        for a_day in range(a, b + 1):
            # 获取当日日期代码,"yyyy-mm-dd"
            today_txt = Trade.get_yyyymmdd_from_all_type(list_of_strategy_days[a_day])
            # 每日算一算当日市值（含现金余额）
            market_value = Trade.market_value_of_position(current_position,today_txt)
        # 当日新策略列表 ["492842","600231",...]
        #更新今日持仓，持仓天数加一
            Trade.update_current_position(current_position,today_txt)
            today_codes_list = strategy_dict[today_txt]
            if len(today_codes_list) == 0:
                continue
            average_value = market_value / len(today_codes_list)

            for old in last_day_codes_list:
                if old not in today_codes_list:
                    old_value = Trade.new_market_value_of_code(old, current_position, today_txt)
                    Orders.sell_value(today_txt, old, old_value)

            for i in current_position:
                if i["股票代码"] == "cash":
                    current_cash = i["持仓价值"]
            n = len(set(today_codes_list) & set(last_day_codes_list))  #当日和前一日购买的相同股票的个数

            if len(today_codes_list)-n > 0:
                average_value = current_cash/(len(today_codes_list)-n)

                for new in today_codes_list:
                    if new not in last_day_codes_list:
                        Orders.buy_value(today_txt, new, average_value)

            dict_current_postion = copy.deepcopy(current_position)
            Trade.update_history_position(history_positions,dict_current_postion,today_txt)

            #建立今日持有股票的副本，作为下一天的昨日购买列表
            last_day_codes_list = copy.deepcopy(today_codes_list)

        return history_positions, current_position, pd_log


starting_cash = 100000000   #初始现金
starting_day = "2020-01-02"  # 2020-1-1至今任意时间开始回测
ending_day = "2021-11-19"

history_positions = {}
pd_log = pd.DataFrame({"日期":[None], "股票代码":[None], "交易类型":[None], "成交数量":[None],"成交价格":[None],"成交额":[None]})
current_position = [{"股票代码":"cash", "持仓数量":None, "持仓价值":starting_cash, "持仓天数":None}]
conStockData = pd.read_excel("2019年至今TOP5股票.xlsx", header=1, index_col=0)

yongjinlv = 0.0003
guohufeilv = 0.00002
yinhuashuilv = 0.001

result = Trade.backtesting()
chicangbiao = result[0]
rizhibiao = result[2]
df = pd.DataFrame.from_dict(chicangbiao, orient='index')
df.to_csv('持仓记录表.csv')
rizhibiao.to_csv('日志表.csv')

